/*
Copyright 2020-2024 New Vector Ltd.

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
*/

import UIKit

@objc protocol BaseRoomCellProtocol: Themable {
    var roomCellContentView: RoomCellContentView? { get }
}

/// `BaseRoomCell` allows a room cell that inherits from this class to embed and manage the default room message outer views and add an inner content view.
@objcMembers
class BaseRoomCell: MXKRoomBubbleTableViewCell, BaseRoomCellProtocol {
    
    // MARK: - Constants
        
    // MARK: - Properties
    
    private var areViewsSetup: Bool = false
    
    // MARK: Public

    weak var roomCellContentView: RoomCellContentView?
    
    private(set) var theme: Theme?
    
    // Overrides
    
    override var bubbleInfoContainer: UIView! {
        get {
            guard let infoContainer = self.roomCellContentView?.bubbleInfoContainer else {
                fatalError("[BaseRoomCell] bubbleInfoContainer should not be used before set")
            }
            return infoContainer
        }
        set {
            super.bubbleInfoContainer = newValue
        }
    }
    
    override var bubbleOverlayContainer: UIView! {
        get {
            guard let overlayContainer = self.roomCellContentView?.bubbleOverlayContainer else {
                fatalError("[BaseRoomCell] bubbleOverlayContainer should not be used before set")
            }
            return overlayContainer
        }
        set {
            super.bubbleInfoContainer = newValue
        }
    }
    
    override var bubbleInfoContainerTopConstraint: NSLayoutConstraint! {
        get {
            guard let infoContainerTopConstraint = self.roomCellContentView?.bubbleInfoContainerTopConstraint else {
                fatalError("[BaseRoomCell] bubbleInfoContainerTopConstraint should not be used before set")
            }
            return infoContainerTopConstraint
        }
        set {
            super.bubbleInfoContainerTopConstraint = newValue
        }
    }
    
    override var pictureView: MXKImageView! {
        get {
            guard let roomCellContentView = self.roomCellContentView,
                roomCellContentView.showSenderAvatar else {
                return nil
            }
            
            guard let pictureView = self.roomCellContentView?.avatarImageView else {
                fatalError("[BaseRoomCell] pictureView should not be used before set")
            }
            return pictureView
        }
        set {
            super.pictureView = newValue
        }
    }
    
    override var userNameLabel: UILabel! {
        get {
            guard let roomCellContentView = self.roomCellContentView, roomCellContentView.showSenderName  else {
                return nil
            }
            
            guard let userNameLabel = roomCellContentView.userNameLabel else {
                fatalError("[BaseRoomCell] userNameLabel should not be used before set")
            }
            return userNameLabel
        }
        set {
            super.userNameLabel = newValue
        }
    }
    
    override var userNameTapGestureMaskView: UIView! {
        get {
            guard let roomCellContentView = self.roomCellContentView,
                roomCellContentView.showSenderName else {
                return nil
            }
            
            guard let userNameTapGestureMaskView = self.roomCellContentView?.userNameTouchMaskView else {
                fatalError("[BaseRoomCell] userNameTapGestureMaskView should not be used before set")
            }
            return userNameTapGestureMaskView
        }
        set {
            super.userNameTapGestureMaskView = newValue
        }
    }
    
    override var readMarkerViewLeadingConstraint: NSLayoutConstraint? {
        get {
            if self is RoomCellReadMarkerDisplayable {
                return self.roomCellContentView?.readMarkerViewLeadingConstraint
            } else {
                return super.readMarkerViewLeadingConstraint
            }
        }
        set {
            if self is RoomCellReadMarkerDisplayable {
                self.roomCellContentView?.readMarkerViewLeadingConstraint = newValue
            } else {
                super.readMarkerViewLeadingConstraint = newValue
            }
        }
    }
    
    override var readMarkerViewTrailingConstraint: NSLayoutConstraint? {
        get {
            if self is RoomCellReadMarkerDisplayable {
                return self.roomCellContentView?.readMarkerViewTrailingConstraint
            } else {
                return super.readMarkerViewTrailingConstraint
            }
        }
        set {
            if self is RoomCellReadMarkerDisplayable {
                self.roomCellContentView?.readMarkerViewTrailingConstraint = newValue
            } else {
                super.readMarkerViewTrailingConstraint = newValue
            }
        }
    }
    
    // MARK: - Setup
            
    required override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        self.commonInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.commonInit()
    }
    
    private func commonInit() {
        self.selectionStyle = .none
        self.setupContentView()
        self.update(theme: ThemeService.shared().theme)
    }
    
    // MARK: - Public
    
    func removeDecorationViews() {
        if let roomCellReadReceiptsDisplayable = self as? RoomCellReadReceiptsDisplayable {
            roomCellReadReceiptsDisplayable.removeReadReceiptsView()
        }
        
        if let roomCellReactionsDisplayable = self as? RoomCellReactionsDisplayable {
            roomCellReactionsDisplayable.removeReactionsView()
        }

        if let roomCellThreadSummaryDisplayable = self as? RoomCellThreadSummaryDisplayable {
            roomCellThreadSummaryDisplayable.removeThreadSummaryView()
        }
        
        if let timestampDisplayable = self as? TimestampDisplayable {
            timestampDisplayable.removeTimestampView()
        }
        
        if let urlPreviewDisplayable = self as? RoomCellURLPreviewDisplayable {
            urlPreviewDisplayable.removeURLPreviewView()
        }
    }
    
    // MARK: - Overrides
    
    override var isTextViewNeedsPositioningVerticalSpace: Bool {
        return false
    }
    
    override func setupViews() {
        super.setupViews()
        
        let showEncryptionStatus = roomCellContentView?.showEncryptionStatus ?? false
        
        if showEncryptionStatus {
            self.setupEncryptionStatusViewTapGestureRecognizer()
        }
    }
    
    override func setupSenderNameLabel() {
        
        guard let userNameTouchMaskView = self.roomCellContentView?.userNameTouchMaskView else {
            return
        }
        
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onSenderNameTap(_:)))
        tapGesture.numberOfTouchesRequired = 1
        tapGesture.numberOfTapsRequired = 1
        tapGesture.delegate = self

        userNameTouchMaskView.addGestureRecognizer(tapGesture)
    }
    
    override func setupAvatarView() {
        
        guard let avatarImageView = self.roomCellContentView?.avatarImageView else {
            return
        }
        
        avatarImageView.mediaFolder = kMXMediaManagerAvatarThumbnailFolder

        // Listen to avatar tap
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onAvatarTap(_:)))
        tapGesture.numberOfTouchesRequired = 1
        tapGesture.numberOfTapsRequired = 1
        tapGesture.delegate = self
        avatarImageView.addGestureRecognizer(tapGesture)
        avatarImageView.isUserInteractionEnabled = true

        // Add a long gesture recognizer on avatar (in order to display for example the member details)
        let longPress = UILongPressGestureRecognizer(target: self, action: #selector(onLongPressGesture(_:)))
        avatarImageView.addGestureRecognizer(longPress)
    }
    
    override class func defaultReuseIdentifier() -> String! {
        return String(describing: self)
    }
    
    override func prepareForReuse() {
        super.prepareForReuse()
        
        self.removeDecorationViews()
    }
    
    override func render(_ cellData: MXKCellData!) {
        // In `MXKRoomBubbleTableViewCell` setupViews() is called in awakeFromNib() that is not called here, so call it only on first render() call
        self.setupViewsIfNeeded()
        
        super.render(cellData)
        
        guard let roomCellContentView = self.roomCellContentView else {
            return
        }
        
        if let bubbleData = self.bubbleData,
            let paginationDate = bubbleData.date,
            roomCellContentView.showPaginationTitle {
            roomCellContentView.paginationLabel.text = bubbleData.eventFormatter.dateString(from: paginationDate, withTime: false)?.uppercased()
        }                
        
        if roomCellContentView.showEncryptionStatus {
            self.updateEncryptionStatusViewImage()
        }
        
        self.updateUserNameColor()
    }
    
    override func customizeRendering() {
        super.customizeRendering()
        self.updateUserNameColor()
    }
    
    // MARK: - Themable
    
    func update(theme: Theme) {
        self.theme = theme
        self.roomCellContentView?.update(theme: theme)
    }
    
    // MARK: - Private

    private func setupViewsIfNeeded() {
        guard self.areViewsSetup == false else {
            return
        }
        self.setupViews()
        self.areViewsSetup = true
    }
        
    private func setupContentView() {
        guard self.roomCellContentView == nil else {
            return
        }
        let roomCellContentView = RoomCellContentView.instantiate()
        self.contentView.vc_addSubViewMatchingParent(roomCellContentView)
        self.roomCellContentView = roomCellContentView
    }
    
    // MARK: - RoomCellURLPreviewDisplayable
    // Cannot use default implementation with ObjC protocol, if self conforms to RoomCellURLPreviewDisplayable method below will be used
    
    func addURLPreviewView(_ urlPreviewView: UIView) {
        self.roomCellContentView?.addURLPreviewView(urlPreviewView)
        
        // tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
        self.addTemporarySubview(urlPreviewView)
    }
    
    func removeURLPreviewView() {
        self.roomCellContentView?.removeURLPreviewView()
    }
    
    // MARK: - RoomCellReadReceiptsDisplayable
    // Cannot use default implementation with ObjC protocol, if self conforms to RoomCellReadReceiptsDisplayable method below will be used
    
    func addReadReceiptsView(_ readReceiptsView: UIView) {
        self.roomCellContentView?.addReadReceiptsView(readReceiptsView)
        
        // tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
        self.addTemporarySubview(readReceiptsView)
    }
    
    func removeReadReceiptsView() {
        self.roomCellContentView?.removeReadReceiptsView()
    }
    
    // MARK: - RoomCellReactionsDisplayable
    // Cannot use default implementation with ObjC protocol, if self conforms to RoomCellReactionsDisplayable method below will be used
    
    func addReactionsView(_ reactionsView: UIView) {
        self.roomCellContentView?.addReactionsView(reactionsView)
        
        // tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
        self.addTemporarySubview(reactionsView)
    }
    
    func removeReactionsView() {
        self.roomCellContentView?.removeReactionsView()
    }

    // MARK: - RoomCellThreadSummaryDisplayable

    func addThreadSummaryView(_ threadSummaryView: ThreadSummaryView) {
        self.roomCellContentView?.addThreadSummaryView(threadSummaryView)
        
        // tmpSubviews is used for touch detection in MXKRoomBubbleTableViewCell
        self.addTemporarySubview(threadSummaryView)
    }

    func removeThreadSummaryView() {
        self.roomCellContentView?.removeThreadSummaryView()
    }
    
    // MARK: - RoomCellReadMarkerDisplayable
            
    func addReadMarkerView(_ readMarkerView: UIView) {
        self.roomCellContentView?.addReadMarkerView(readMarkerView)
        self.readMarkerView = readMarkerView
    }
    
    override func removeReadMarkerView() {
        self.roomCellContentView?.removeReadMarkerView()
        
        super.removeReadMarkerView()
    }
    
    // Encryption status
    
    private func updateEncryptionStatusViewImage() {
        guard let component = self.bubbleData.getFirstBubbleComponentWithDisplay() else {
            return
        }
        self.roomCellContentView?.encryptionImageView.image = RoomEncryptedDataBubbleCell.encryptionIcon(for: component)
    }
    
    private func setupEncryptionStatusViewTapGestureRecognizer() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleEncryptionStatusContainerViewTap(_:)))
        tapGestureRecognizer.delegate = self
        self.roomCellContentView?.encryptionImageView.isUserInteractionEnabled = true
    }
    
    @objc private func handleEncryptionStatusContainerViewTap(_ gestureRecognizer: UITapGestureRecognizer) {
        guard let delegate = self.delegate else {
            return
        }
        
        guard let component = self.bubbleData.getFirstBubbleComponentWithDisplay() else {
            return
        }
                
        let userInfo: [AnyHashable: Any]?
        
        if let tappedEvent = component.event {
            userInfo = [kMXKRoomBubbleCellEventKey: tappedEvent]
        } else {
            userInfo = nil
        }
                
        delegate.cell(self, didRecognizeAction: kRoomEncryptedDataBubbleCellTapOnEncryptionIcon, userInfo: userInfo)
    }
}
